Загрузка...
 
Печать

DirectX 8 Graphics. Текстурирование


Содержание



Intro


Image
Рис.1 Текстурирование позволяет наносить на полигоны любые изображения

Image
Рис.2 Наложение текстуры на грань с использованием UV текстурных координат


Полигоны, окрашенные в сплошные цвета смотрятся абстрактно. Отличный способ повысить реализм 3D-сцены - применить к объектам текстуры.1 Техника нанесения на полигоны растровых (bitmap) изображений называется текстурирование (texture-mapping). Bitmap-изображения здесь называются текстурами или текстурными картами (См. Рис.1).
При текстурировании программер назначает каждой вершине полигона дополнительные пары координат (т.н. UV-координаты), каждая пара которых определяет точку на текстуре. UV-координаты аналогичны XY-координатам текстуры, с той лишь разницей, что UV-координаты основаны на ширине (width) и высоте (height) текстуры и варьируются в диапазоне от 0.0 до 1.0 .
В обычном программировании X и Y координаты изображения варьируются от 0 до ширины или высоты изображения. Т.е. у изображения размером 640х480 координата X будет варьироваться от 0 до 639, а координата Y от 0 до 479. Для доступа к координатам средней точки изображения в этом случае указывают координаты X=319 и Y=239.
Координаты U и V отсчитываются от 0 (начиная с верхнего или левого края изображения). Для доступа к средней точке (расположенной посередине) любого изображения используют координаты U=0.5 и V=0.5 (См. Рис.2).
Закрыть
noteЛюбопытно

Есть такой трюк. Если присвоить U или V координатам значения больше 1.0, то текстура выйдет за свои пределы, "обернув" полигоны вокруг. Например если координате U присвоить значение 2.0, то текстура прорисуется дважды (стыкуясь одна к другой) по горизонтали. Значение 3.0 означает, что текстура прорисуется трижды. Аналогично работает "перебор" с координатами для V-координаты.

Присвоение вершинам UV-координат текстуры, вместо привычных XY, может сперва показаться излишним. Но на деле это отличное подспорье, т.к. мы можем быстро сменять (swap) текстуры разных размеров, не заботясь об их реальных метрических данных.
Вообще текстурой может быть любое цифровое изображение. Многие игровые движки поддерживают текстуры форматов JPG, GIF, PNG и многих других. В нашем случае мы будем использовать классический вариант - текстуры в формате растровых изображений (BMP). Многие современные видеокарты поддерживают т.н. бамп-мэпинг (bump-mapping) - технологию, Рис.2 Наложение текстуры на грань с использованием UV текстурных координат которая выявляет на изображении "неровности" (тёмные участки) и генерирует на их основе шероховатую поверхность (с выбоинами), придавая полигональным 3Р-объектам ещё более реалистичный вид.

Для компиляции примеров на понадобится:
  • MS Visual C++ 2010 Express Edition,
  • Microsoft DirectX SDK 8.
Всё легко гуглится + есть в разделе "Софт" нашего сайта.

Текстурирование в Direct3D (Using Texture-Mapping with Direct3D; DirectX 8)

Текстуры в Direct3D контролирует специальный объект IDirect3DTexture8, содержащий в себе информацию о текстуре, и предоставляющий доступ к ней (включая указатель на данные пикселя (pixel data) изображения текстуры). Direct3D (а также производители компьютерного железа) накладывают ряд ограничений на использование текстур. Во-первых, ограничены максимальные размеры текстуры, которые при этом обязательно должны увеличиваться в квадратичной прогрессии (т.е. каждое новое значение - это предыдущее значение, возведённое в квадрат: 8, 32, 128, 256 и т.д.). Многие видеокарты (образца начала 2000-х годов) вообще признают только равносторонние (=квадратные) текстуры (32х32, 64х64 и т.д.). По этой причине старайся всегда использовать только равносторонние текстуры. Они хоть всегда будут поддерживаться на компьютере игрока. По этой же причине многие игры эпохи первого Half Life поддерживали текстуры размером не более 256х256 пикселей. Современные видеокарты "тянут" куда более крупные текстуры. Но без крайней необходимости размеры текстур лучше оставлять минимальными.
Не используй слишком много текстур. В то время как процесс рендеринга уже затекстурированного полигона прост и быстр, подготовка текстуры к использованию может потребовать много ресурсов. Всякий раз, когда игре нужна новая текстура, Direct3D и видеокарта должны слегка потрудиться, чтобы сперва подготовить её к рендерингу.
Закрыть
noteСовет

Многие игрокодеры старой школы для снижения времени, затрачиваемого на подготовку текстуры к рендерингу, просто комбинировали ("склеивали") несколько текстур в одну большую (т.н. tilemap). Такой подход обеспечивал подготовку всех текстур "в один проход". Затем движок выбирал области на большой текстуре по мере необходимости.

Процесс подготовки текстуры к рендерингу включает в себя:
  • копирование текстуры в подходящую память (ОЗУ или видеопамять);
  • назначение текстуре цветового формата, совпадающего с экранным (впрочем, не всегда).
Всё это занимает драгоценное процессорное время. И чем меньше комп этим занимается, тем лучше.

Загрузка текстуры (Loading a Texture; DirectX 8)

На начальном этапе файл изображения считывается с жёсткого диска или другого носителя. Уже знакомая нам вспомогательная библиотека D3DX содержит набор функций для загрузки и управления текстурами:
ФУНКЦИЯ ОПИСАНИЕ
D3DXCreateTextureFromFile Загружает изображение текстуры из растрового (bitmap) файла на диске.
D3DXCreateTextureFromFileEx Более продвинутая версия функции D3DXCreateTextureFromFile.
D3DXCreateTextureFromFileInMemory Загружает текстурное изображение из файла, который уже загружен в память.
D3DXCreateTextureFromFileInMemoryEx Более продвинутая версия функции D3DXCreateTextureFromFileInMemory.
D3DXCreateTextureFromResource Загружает текстурное изображение из файла двоичных ресурсов.
D3DXCreateTextureFromResourceEx Более продвинутая версия функции D3DXCreateTextureFromResource.

Видим, что каждая из трёх функций представлена в двух версиях:
  • одна для быстрой загрузки текстур безо всяких "наворотов",
  • другая (с окончанием Ex) более продвинута и даёт бОльший контроль над процессом загрузки текстуры.
Начнём с применения функции D3DXCreateTextureFromFile. Вот её прототип:
Прототип функции D3DXCreateTextureFromFile
HRESULT D3DXCreateTextureFromFile(
	IDirect3DDevice8 *pDevice, // Предварительно проинициализированный объект устройства Direct3D.
	LPCSTR pSrcFile, // Имя файла растрового изображения.
	IDirect3DTexture8 **ppTexture // Имя создаваемого объекта текстуры.
	);

Функция несложная. Указываем объект устройства Direct3D, с которым работаем, имя файла текстуры и указатель на объект, в который текстура будет сохранена.
Вот пример применения (загружаем текстуру из файла texture.bmp)
// g_pD3DDevice - предварительно проинициализированный объект устройства Direct3D.
IDirect3DTexture8 *pD3DTexture;

if(FAILED(D3DXCreateTextureFromFile(g_pD3DDevice, "texture.bmp", (void**)&pD3DTexture)))
{
	// Ошибка.
}

Функция сама проделывает всю инициализацию и "стыкует" текстуру к классу D3DPOOL_MANAGED, сохраняя её в памяти. Однажды созданная и сохранённая в памяти текстура может использоваться сколько угодно раз. По окончании работы с ней текстуру удаляют. Неудалённые (т.н. "утерянные") текстуры являлись головной болью для всех, кто работал с ними в более ранних версиях DirectX SDK.

Назначение текстуры полигону. Текстурные уровни (Setting the Texture. Texture Stages; DirectX 8)

Как было сказано выше, объект устройства Direct3D перед рендерингом текстуры должен сперва сам себя подготовить. Если точнее, то подготовка идёт перед рендерингом полигона, с нанесённой на него текстурой. Если у тебя 1000 полигонов, каждый из которых использует свою уникальную текстуру, необходимо пройти цикл такой подготовки для каждого полигона. И уже затем каждому из них назначается текстура и всё это рендерится. Весь процесс повторяется для каждого полигона. Если несколько полигонов используют одну и ту же текстуру, то эффективнее будет организовать цикл "назначение текстуры - рендеринг всех полигонов, которые её используют".
Для назначения текстуры используется функция IDirect3DDevice8::SetTexture. Вот её прототип:
Прототип функции SetTexture
HRESULT IDirect3DDevice8::SetTexture(
	DWORD Stage, // "Сцена" текстуры (0-7)
	IDirect3DBaseTexture8 *pTexture // Объект текстуры, который назначается.
	);


Image
Рис.3 Каждый текстурный уровень изменяет выходной пиксель разными способами. Здесь входной пиксель проходит через несколько стадий преобразований


Первый параметр представляет собой т.н. текстурный уровень (texture stage). Применяется при мультитекстурировании и является одной из самых крутых фишек Direct3D. Каждая из максимально возможных восьми текстур может быть помещена в один из этих уровней. То есть, в первый уровень мы, допустим, устанавливаем исходную текстуру, а во второй ту, что будем накладывать сверху. Первый параметр этой функции как раз и указывает номер уровня, в который необходимо поместить текстуру. Текстурирование в Direct3D гибко настраивается. Текстура необязательно должна загружаться из одного источника. Таких источников может быть от 1 до 8. Они и называются текстурными уровнями (См. Рис.3).
Во время рендеринга полигона для каждого пикселя текстуры Direct3D начинает поочерёдно опрашивать все 8 уровней, начиная с первого (0), на предмет наличия текстурного пикселя. Если пиксель обнаружен на первом уровне, то либо происходит переход на следующий, либо модифицируется текстурный пиксель на предыдущем. Данный процесс продолжается, пока не будут опрошены все 8 текстурных уровня. На каждом текстурном уровне программер может изменять текстурный пиксель. Например, можно включить смешивание (blending) пикселя с предыдущего уровня с пикселем следующего. Можно увеличить/уменьшить насыщенность, светлость (brightness) и даже применить специальный эффект, известный как альфа-смешивание (alpha-blending; техника, которая смешивает цвета нескольких пикселей).
Закрыть
noteЛюбопытно

Забегая вперёд скажем, что на основе мультитекстурирования построена технология бамп-мэпинга (bump-mapping(external link)).

Такой подход даёт широкие возможности по воплощению самых смелых текстурных замыслов. В данной статье, для простоты изложения, мы будем использовать всего 1 текстурный уровень, который:
  • просто выделяет цвет пикселя из текстуры,
  • применяет к текстурному пикселю инфу о цветах вершин полигона,
  • рендерит окрашенный пиксель, нанесённый на полигон.
Следующий фрагмент кода устанавливает текстуру на текстурный уровень 0 и указывает рендереру получить из него текстурный пиксель, применить к нему информацию о цветах вершин полигона и отключает альфа-смешивание (alpha-blending):
// g_pD3DDevice - предварительно проинициализированный объект устройства Direct3D.
// pD3DTexture - предварительно созданный и проинициализированный объект текстуры.

// Устанавливаем текстуру на текстурный уровень 0.
g_pD3DDevice->SetTexture(0, pTexture);

// Устанавливаем параметры текстурного уровня (необходимо проделать всего 1 раз).
g_pD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
g_pD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
g_pD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
g_pD3DDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE);

Это лишь самый базовый набор операций с текстурой. Сама текстура устанавливается на данный текстурный уровень лишь однажды. Более подробная информация по использованию текстурных уровней как всегда есть в документации к DirectX SDK.
По окончании использования текстуры (после завершения рендеринга полигона) снова вызываем функцию SetTexture, указывая во втором параметре NULL:
g_pD3DDevice->SetTexture(0, NULL);

Это удалит текстуру из памяти. Пропуск данной операции грозит утечками памяти (memory leaks) и последующей нестабильной работой игрового приложения.

Применение текстурных фильтров (Using Texture Filters; DirectX 8)

Фильтрация текстур вступает в игру при рендеринге уже текстурированных полигонов. Т.к. разрешение экрана ограничено, выводимое изображение с полигонами может иметь различные нежелательные артефакты (зубчатые грани при прорисовке диагональных линий; пикселизованные (чрезмерно увеличенные) фрагменты изображения текстуры, появляющиеся при увеличении объекта/приближении камеры).
Для этих (и многих других) косяков были придуманы текстурные фильтры. Direct3D применяет ряд фильтров, заметно улучшающих вид уже отрендеренного изображения.
Для применения фильтра вызывают специальную функцию IDirect3DDevice8::SetTextureStageState. Вот её прототип:
Прототип функции IDirect3DDevice8::SetTextureStageState
HRESULT IDirect3DDevice8::SetTextureStageState(
	DWORD Stage, // Текстурный уровень (0-7).
	D3DTEXTURESTAGETYPE Type, // Состояние (state) уровня.
	DWORD Value // Применяемое значение.
	);

И вновь видим активное применение текстурных уровней. Но второй и третий параметры здесь уже другие. Параметр Type представляет собой состояние (state) текстурного уровня, с которым в данный момент работаем. В нашем случае возьмём одно из двух значений: D3DTSS_MAGFILTER (для увеличения масштаба текстуры прямо на полигоне) или D3DTSS_MINFILTER (для уменьшения текстуры на полигоне). Оба этих состояния определяют, как Direct3D будет смешивать прилегающие пиксели внутри текстуры (прежде чем вывести пиксель на экран). Параметр Value может принимать одно из следующих значений:
ЗНАЧЕНИЕ ОПИСАНИЕ
D3DTEXF_NONE Не применять фильтр.
D3DTEXF_POINT Самый быстрый алгоритм фильтрации. Использует цвет одного пикселя для создания текстурной карты.
D3DTEXF_LINEAR Метод билинейной интерполяции (bilinear interpolation). Комбинирует 4 пикселя текстурной карты для получения одного пикселя смешанного цвета. Тоже оч. быстрый метод текстурирования, придающий текстуре хороший, сглаженный вид.
D3DTEXF_ANISOTROPIC Метод анизотропной (anisotropic) фильтрации. Учитывает угол между плоскостью экрана и текстурированного полигона. Выглядит хорошо, но работает относительно медленно.

На деле обычно применяют всего 2 режима фильтрации: D3DTEXF_POINT и D3DTEXF_LINEAR. Вот пример использования фильтрации текстуры:
// g_pD3DDevice - предварительно проинициализированный объект устройства Direct3D.
// Назначаем фильтр увеличения (magnification filter).
if(FAILED(g_pD3DDevice->SetTextureStageState(0, D3DTS_MAGFILTER, D3DTEXF_POINT)))
{
	// Ошибка.
}

// Назначаем фильтр уменьшения (minification filter).
if(FAILED(g_pD3DDevice->SetTextureStageState(0, D3DTS_MINFILTER, D3DTEXF_POINT)))
{
	// Ошибка.
}


Рендеринг текстурированных объектов (Rendering Textured Objects; DirectX 8)

Прежде чем рендерить объект (или группу полигонов) с текстурой, необходимо убедиться, что вершины полигона содержат пары координат UV. Кастомная FVF-структура, как правило, содержит набор 3D-координат вершины и текстурные координаты в таком виде:
typedef struct
{
	D3DVECTOR3 Position; // Вектор позиции вершины.
	float tu, tv; // Здесь добавляем текстурные координаты.
}sVertex;

Теперь можно создавать свой FVF-формат для информирования Direct3D об используемых компонентах вершины. На данном этапе здесь задействованы нетрансформированные 3D-координаты вершины и пара текстурных координат (tv и tu). Далее определим нашу FVF-структуру так:
#define VERTEXFMT (D3DFVF_XYZ | D3DFVF_TEX1)

Теперь можно выводить графику на экран. Путём добавления всего нескольких строк кода мы существенно расширили функцию простой отрисовки полигона, которая теперь включает в себя текстурные координаты. Предположим, ты проинициализировал объект устройства Direct3D, определил (defined) вершинный буфер (vertex buffer) с информацией о текстуре, а также установил каждую из трёх матриц:
  • мировая матрица (world matrix),
  • матрица вида (view matrix),
  • матрица проекции (projection matrix).
Тогда можно рассмотреть такой пример загрузки текстуры и её применения к полигонам при отрисовке листа треугольников (triangle list):
// g_pD3DDevice - предварительно проинициализированный объект устройства Direct3D.
// NumPolys - число выводимых полигонов.
// g_pD3DVertexBuffer - предварительно созданный вершинный буфер, заполненный инфой о вершинах.
IDirect3DTexture8 *pD3DTexture; // Объект текстуры.

// Загружаем текстуру.
D3DXCreateTextureFromFile(g_pD3DDevice, "texture.bmp", (void**)&pD3DTexture);

if(SUCCEEDED(g_pD3DDevice->BeginScene ()))
{
	// Назначаем текстуру.
	g_pD3DDevice->SetTexture(0, pD3DTexture);
	
	// Назначаем источник потока (Stream Source) и вершинный шейдер (Vertex Shader).
	g_pD3DDevice->SetStreamSource(0, g_pD3DVertexBuffer, sizeof(sVertex);
	g_pD3DDevice->SetVertexShader(VERTEXFMT);
	
	// Отрисовываем список треугольников (triangle list).
	g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, NumPolys);
	
	// Завершаем сцену.
	g_pD3DDevice->EndScene();
	
	// Освобождаем память с текстурой.
	g_pD3DDevice->SetTexture(0, NULL);
}


Пример приложения, выводящее в окно 2D-изображение (Win32, DirectX8)

Напомним, что начиная с 8 версии DirectX выводит 2D-изображения только средствами Direct3D.

Создаём Проект приложения Draw2D_01

  • Создай пустой Проект с именем Draw2D_01.
Проект автоматически разместится внутри Решения с таким же именем. Весь процесс подробно расписан в статье MS Visual Cpp 2010 Express. Установка и указание путей к DirectX SDK.

Добавляем в Проект WinMain.cpp

Для чистоты эксперимента мы создали пустой Проект, т.е. без каких-либо файлов в нём. Создадим единственный файл с исходным кодом WinMain.cpp.
  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Файлы исходного кода" Проекта Draw2D_01.
  • Во всплывающем меню Добавить->Создать элемент...
Image
  • В появившемся окне выбери "Файл С++ (.cpp)" и в поле "Имя" введи WinMain.cpp. Жмём "Добавить".
Image
Добавленный файл сразу откроется в правой части MSVC++2010.
  • В только что созданном и открытом файле WinMain.cpp набираем следующий код:
WinMain.cpp
/**************************************************
WinMain.cpp
Chapter 6 2-D Drawing Demo (Draw2D_01)

Programming Role-Playing Games with DirectX
by Jim Adams (01 Jan 2002)

Required libraries:
  D3D8.LIB and D3DX8.LIB
**************************************************/

// Include files
#include <windows.h>
#include <stdio.h>
#include "d3d8.h"
#include "d3dx8.h"

// Window handles, class and caption text
// Дескриптор окна, оконный класс и текст на тулбаре
HWND          g_hWnd;
HINSTANCE     g_hInst;
static char   g_szClass[]   = "Draw2DClass";
static char   g_szCaption[] = "Draw2D Demo by Jim Adams";

// The Direct3D and Device object
IDirect3D8       *g_pD3D       = NULL;
IDirect3DDevice8 *g_pD3DDevice = NULL;

// The 2-D vertex format and descriptor
// FVF-структура и её дескриптор
typedef struct {
  FLOAT x, y, z;     // 2-D coordinates
  FLOAT rhw;         // rhw
  FLOAT u, v;        // Texture coordinates
} sVertex;
#define VERTEXFVF (D3DFVF_XYZRHW | D3DFVF_TEX1)

// Vertex buffer
// Вершинный буфер
IDirect3DVertexBuffer8 *g_pVB = NULL;

// Texture
IDirect3DTexture8 *g_pTexture = NULL;

// Function prototypes
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev,          \
                   LPSTR szCmdLine, int nCmdShow);
long FAR PASCAL WindowProc(HWND hWnd, UINT uMsg,              \
                           WPARAM wParam, LPARAM lParam);

BOOL DoInit();
BOOL DoShutdown();
BOOL DoFrame();
BOOL SetupMeshes();

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev,          \
                   LPSTR szCmdLine, int nCmdShow)
{
  WNDCLASSEX wcex;
  MSG        Msg;

  g_hInst = hInst;

  // Create the window class here and register it
  // Создаём оконный класс и регистрируем его
  wcex.cbSize        = sizeof(wcex);
  wcex.style         = CS_CLASSDC;
  wcex.lpfnWndProc   = WindowProc;
  wcex.cbClsExtra    = 0;
  wcex.cbWndExtra    = 0;
  wcex.hInstance     = hInst;
  wcex.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
  wcex.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wcex.hbrBackground = NULL;
  wcex.lpszMenuName  = NULL;
  wcex.lpszClassName = g_szClass;
  wcex.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);
  if(!RegisterClassEx(&wcex))
    return FALSE;

  // Create the Main Window
  // Создаём главное окно программы
  g_hWnd = CreateWindow(g_szClass, g_szCaption,
        WS_CAPTION | WS_SYSMENU,
        0, 0, 400, 400,
        NULL, NULL,
        hInst, NULL );
  if(!g_hWnd)
    return FALSE;
  ShowWindow(g_hWnd, SW_NORMAL);
  UpdateWindow(g_hWnd);

  // Run init function and return on error
  if(DoInit() == FALSE)
    return FALSE;

  // Start message pump, waiting for signal to quit
  // Стартуем процедуру выборки сообщений
  ZeroMemory(&Msg, sizeof(MSG));
  while(Msg.message != WM_QUIT) {
    if(PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE)) {
      TranslateMessage(&Msg);
      DispatchMessage(&Msg);
    }
    if(DoFrame() == FALSE)
      break;
  }

  // Run shutdown function
  DoShutdown();
  
  UnregisterClass(g_szClass, hInst);

  return Msg.wParam;
}

long FAR PASCAL WindowProc(HWND hWnd, UINT uMsg,              \
                           WPARAM wParam, LPARAM lParam)
{
  switch(uMsg) {
    case WM_DESTROY:
      PostQuitMessage(0);
      return 0;

  }

  return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

BOOL DoInit()
{
  D3DPRESENT_PARAMETERS d3dpp;
  D3DDISPLAYMODE        d3ddm;
  BYTE *Ptr;
  sVertex Verts[4] = {
      {  50.0f,  50.0f, 1.0f, 1.0f, 0.0f, 0.0f },
      { 350.0f,  50.0f, 1.0f, 1.0f, 1.0f, 0.0f },
      {  50.0f, 350.0f, 1.0f, 1.0f, 0.0f, 1.0f },
      { 350.0f, 350.0f, 1.0f, 1.0f, 1.0f, 1.0f }
    };

  // Do a windowed mode initialization of Direct3D
  // Выполняем инициализацию объекта устройства Direct3D
  // для оконного режима
  if((g_pD3D = Direct3DCreate8(D3D_SDK_VERSION)) == NULL)
    return FALSE;
  if(FAILED(g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm)))
    return FALSE;
  ZeroMemory(&d3dpp, sizeof(d3dpp));
  d3dpp.Windowed = TRUE;
  d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
  d3dpp.BackBufferFormat = d3ddm.Format;
  d3dpp.EnableAutoDepthStencil = FALSE;
  if(FAILED(g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hWnd,
                                  D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                  &d3dpp, &g_pD3DDevice)))
    return FALSE;

  // Create the vertex buffer and set data
  // Создаём вершинный буфер и устанавливаем данные
  g_pD3DDevice->CreateVertexBuffer(sizeof(sVertex)*4, 0,      \
                          VERTEXFVF, D3DPOOL_DEFAULT, &g_pVB);
  g_pVB->Lock(0,0, (BYTE**)&Ptr, 0);
  memcpy(Ptr, Verts, sizeof(Verts));
  g_pVB->Unlock();

  // Load the texture map
  D3DXCreateTextureFromFile(g_pD3DDevice, "Texture.bmp", &g_pTexture);

  return TRUE;
}

BOOL DoShutdown()
{
  // Release vertex buffer
  // Очищаем вершинный буфер
  if(g_pVB != NULL)
    g_pVB->Release();

  // Release texture
  if(g_pTexture != NULL)
    g_pTexture->Release();

  // Release device and 3D objects
  if(g_pD3DDevice != NULL)
    g_pD3DDevice->Release();

  if(g_pD3D != NULL)
    g_pD3D->Release();

  return TRUE;
}

BOOL DoFrame()
{
  // Clear device backbuffer
  // Очищаем бэкбуфер устройства
  g_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET,               \
                        D3DCOLOR_RGBA(0,64,128,255), 1.0f, 0);

  // Begin scene
  if(SUCCEEDED(g_pD3DDevice->BeginScene())) {

    // Set the vertex stream, shader, and texture
	// Назначаем вершинный поток, шейдер и текстуру
    g_pD3DDevice->SetStreamSource(0, g_pVB, sizeof(sVertex));
    g_pD3DDevice->SetVertexShader(VERTEXFVF);
    g_pD3DDevice->SetTexture(0, g_pTexture);

    // Draw the vertex buffer
	// Отрисовываем вершинный буфер
    g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

    // Release texture
    g_pD3DDevice->SetTexture(0, NULL);

    // End the scene
    g_pD3DDevice->EndScene();
  }

  // Display the scene
  // Выводим сцену на экран
  g_pD3DDevice->Present(NULL, NULL, NULL, NULL);

  return TRUE;
}


  • Сохрани Решение (Файл -> Сохранить все).
Данный исходный код целиком взят из примера к книге Programming Role-Playing Games with DirectX by Jim Adams (01 Jan 2002). Автор прогал на MS Visual C++ 6.0 (вышла в свет в 1998 г.) c установленным DirecX SDK 8.0. И если в нашем случае в качестве Graphic API мы юзаем практически идентичный DirectX SDK 8.1, то IDE у нас совсем другая (MS Visual C++ 2010 Express Edition). Нет, не с целью усложнения задачи. Просто MS Visual C++ 6.0 в своё время стоила 500 USD. Сейчас её, в принципе, можно поискать в торрентах. (За всё, что ты там скачаешь, администрация Igrocoder.ru ответственности не несёт!) Но использование платного/пиратского софта не соответствует концепции сайта Igrocoder.ru . Поэтому наш выбор:
  • Бесплатная IDE MS Visual C++ 2010 Express Edition;
  • Бесплатный DirectX SDK 8.1 .
С момента выхода MS Visual C++ 6.0 прошло немало времени. С MSVC++2010 Express их разделяют аж 12 лет. Из-за этого вышеприведённый код (написанный в начале 2002 г.) на данном этапе в MSVC++2010 Express компилироваться не будет, выдавая многочисленные ошибки. Даже с настроенными путями к DirectX SDK.
Сейчас мы это исправим.

Готовим Проект Draw2D_01 к компиляции

Для успешной компиляции изменим настройки (=свойства) текущего Проекта Draw2D_01, созданного в MSVC++2010. При этом сам код из книги 2002 года останется нетронутым.
Закрыть
noteОбрати внимание

Напомним, что такую настройку необходимо повторно проделывать при создании каждого нового Проекта. Ниже представлен алгоритм действий по настройке Проекта, созданного в MSVC++2010 Express с применением DirectX SDK. При создании приложений под платформы, отличные от Win32, либо применении более новых версий DirectX SDK, процесс конфигурирования Проекта может отличаться от приведённого ниже.


Указываем пути к DirectX SDK 8.1

Если попытаться скомпилировать Проект Draw2D_01 в таком виде, то ничего не выйдет.
В единственном файле исходного кода WinMain.cpp можно увидеть инклуды различных заголовочных файлов DirectX (весии 8):
Фрагмент WinMain.cpp
...
// Include files
#include <windows.h>
#include <stdio.h>
#include "d3d8.h"
#include "d3dx8.h"
...

На данном этапе MSVC++2010 ничего не знает об их местоположении. В статье MS Visual Cpp 2010 Express. Установка и указание путей к DirectX SDK мы указывали пути к DirectX SDK (версии 9 и выше). Для DirectX SDK 8 это делается аналогично. Начнём.
  • Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект Draw2D_01.
  • Убедись, что DirectX SDK 8.1 установлен на компьютере и ты уверенно можешь назвать полный путь к его каталогу.
В Обозревателе решений видим: "Решение "Draw2D_01"", а строкой ниже жирным шрифтом название Проекта (тоже Draw2D_01).
  • Жмём правой кнопкой мыши по названию Проекта Draw2D_01. Во всплывающем меню выбираем пункт "Свойства".
Image
Или в Главном меню выбираем Проект->Свойства. Или нажимаем Alt+F7.
В появившемся меню свойств проекта выбираем Свойства конфигурации -> Каталоги VC+ + . В правой части этой страницы расположены пути ко всевозможным каталогам. Здесь нас интересуют только 2 строки: Каталоги включения и Каталоги библиотек.

Указываем каталог включений (include) DirectX SDK 8.1
  • В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги включения. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
Image
В появившемся меню "Каталоги включения" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к заголовочным файлам DirectX SDK 8.1 (include). В нашем случае это C:\DXSDK8\include. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.
Image
  • Жмём "ОК".

Указываем каталог библиотек (lib) DirectX SDK 8.1
  • В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги библиотек. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
  • В появившемся меню "Каталоги библиотек" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к 32-разрядным версиям файлов библиотек DirectX SDK 8.1 (lib). В нашем случае это c:\DXSDK8\lib\. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.
Image
  • Жмём "ОК".
  • На Странице свойств тоже жмём "ОК".
  • Сохрани Решение (File -> Save All).
Готово.

Указываем пути к Windows SDK

Помимо указания путей к заголовкам (include) и библиотекам (lib) DirectX SDK, для любого DirectX-Проекта также необходимо указать пути к заголовкам (include) и библиотекам (lib) Windows SDK. Самое смешное, что в MS Visual C++ 2010 эти пути указываются по умолчанию для каждого создаваемого Проекта. В этом нетрудно убедиться, если ещё раз открыть Проект -> Свойства -> Свойства конфигурации - >Каталоги VC++ -> Каталоги включения -> Изменить. В окне "Каталоги включения" в нижней (недоступной для редактирования) части видим список "Унаследованные значения", где в третьей строке стоит значение:
$(WindowsSdkDir)include

В результате видим, что добавленные пути к DirectX SDK (в обоих окнах: include и lib) расположены вверху списка, а пути к Windows SDK - в недоступной области, на несколько строк ниже.
Но! Заголовочным файлам DirectX SDK "жизненно важно", чтобы они включались после включений заголовков Windows SDK.
Закрыть
noteВажно!

Каталоги, пути к которым указаны в окнах "Каталоги включения" и "Каталоги библиотек" при компиляции считываются один за другим по списку сверху вниз. Поэтому для корректного указания путей надо разместить пути к Windows SDK выше, а к DirectX SDK - ниже по списку (чтобы они считывались последними).

В данной ситуации мы не можем поднять пути к Windows SDK, прописанные по умолчанию при создании Проекта, т.к. они расположены в специальной нередактируемой области (ограничение бесплатной версии MSVC++2010 Express). Но можем схитрить и добавить ещё раз пути к тем же самым каталогам Windows SDK, подняв эти строки выше строк DirectX SDK.
Выше мы указывали пути к DirectX SDK. Для путей к Windows SDK это делается аналогично. Начнём.
  • Убедись, что MSVC++2010 запущена и в ней открыт Проект, с которым работаешь в данный момент.
  • Убедись, что Windows SDK (в нашем случае версия 7) установлен на компьютере и ты уверенно можешь назвать полный путь к его каталогу.
В Обозревателе решений видим: "Решение "..."", а строкой ниже жирным шрифтом название Проекта с тем же названием (если не менял вручную).
  • Жмём правой кнопкой мыши по названию Проекта. Во всплывающем меню выбираем пункт "Свойства".
Image
Или в Главном меню выбираем Проект->Свойства. Или нажимаем Alt+F7.
В появившемся меню свойств проекта выбираем Свойства конфигурации -> Каталоги VC++ . В правой части этой страницы расположены пути ко всевозможным каталогам. Здесь нас, как и в прошлый раз, интересуют только 2 строки: Каталоги включения и Каталоги библиотек.

Указываем каталог включений (include) Windows SDK
  • В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги включения. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
Image
В появившемся меню "Каталоги включения" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к заголовочным (include) файлам Windows SDK. В нашем случае (Win7 x64) это C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Include. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.
Image
  • Меняй порядок считывания каталогов включений с помощью кнопок с чёрными стрелками в верхней части окна "Каталоги вложений".
Каталог включений DirectX SDK должен всегда стоять в самом конце списка, как на этом скриншоте:
Image
  • Жмём "ОК".

Указываем каталог библиотек (lib) Windows SDK
  • В меню свойств Проекта щёлкаем левой кнопкой мыши по пункту Каталоги библиотек. В правой части этой строки видим кнопку с чёрным треугольником, указывающим на наличие выпадающего меню. Нажимаем на неё -> выбираем "Изменить..."
  • В появившемся меню "Каталоги библиотек" жмём кнопку "Создать строку" (с жёлтой папкой) и указываем полный путь к папке с файлами библиотек (lib) Windows SDK. В нашем случае (Win7 x64) это C:\Program Files (x86)\MicrosoftSDKs\Windows\v7.0A\Lib. Можно просто выбрать каталог из дерева каталогов, нажав кнопку с троеточием, расположенную справа от строки ввода.
Image
  • Меняй порядок считывания каталогов вложений с помощью кнопок с чёрными стрелками в верхней части окна.
Каталог библиотек DirectX SDK должен всегда стоять в самом конце списка, как на этом скриншоте:
Image
  • Жмём "ОК".
  • На Странице свойств тоже жмём "ОК".
  • Сохрани Решение (File -> Save All). Готово.

Выбираем многобайтовую кодировку

Закрыть
noteПримечание

В MS Visual C++ 2010 в настройках по умолчанию стоит набор (кодировка) символов UNICODE. В MS Visual C++ 6.0 - напротив, по умолчанию стоит кодировка ANSI (многобайтовая). Данная настройка сильно влияет на типы используемых переменных, что приводит к заметным различиям в исходном коде.
Несмотря на то, что во всех случаях рекомендуется использовать кодировку UNICODE, поддерживаемую во всех современных ОС семейства MS Windows (начиная с Win 2000/XP), большинство книг по программированию игр на классическом C++ придерживаются именно многобайтовой кодировки. Чтобы сильно не переделывать исходные коды под UNICODE, данный Проект мы настроим под многобайтовую кодировку.

Чтобы сильно не переделывать исходные коды под UNICODE, все наши игровые Проекты мы настроим под многобайтовую кодировку. Для этого...
  • Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект Draw2D_01.
В Обозревателе решений видим: "Решение "Draw2D_01"", а строкой ниже жирным шрифтом название Проекта (тоже Draw2D_01).
  • В Обозревателе решений щёлкаем правой кнопкой мыши по названию Проекта Draw2D_01.
  • Во всплывающем контекстном меню выбираем "Свойства".
  • В появившемся окне установки свойств Проекта жмём Свойства конфигурации->Общие, в правой части в строке "Набор символов" выставляем значение "Использовать многобайтовую кодировку".
Image
  • Жмём ОК.
  • Сохрани Решение (Файл->Сохранить все)

Отключаем инкрементную компоновку (incremental linking)

Инкрементная компоновка призвана сократить время компилирования. Но на деле её присутствие часто вызывает ошибки вроде этой:
Error LNK1123: сбой при преобразовании в COFF: файл недопустим или поврежден


Закрыть
warningОшибка LINK : fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt

В MS Visual C++ 2010 даже компиляция консольных приложений нередко завершается неудачей, а вместо исполняемого файла программист видит сообщение:
LINK : fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt.

ПРИЧИНА: MS Visual С++ 2010 "не нравится" версия .NET Framework(external link), установленная в операционной системе. В общих чертах, MS Visual С++ 2010 спрограммирована для работы под управлением .NET Framework 4.0 и сильно к нему привязана. Если точнее, к нему сильно привязана система инкрементной компоновки приложений (incremental linking), которая по умолчанию включена для всех создаваемых проектов.
Во время установки MS Visual C++ 2010 пытается установить свой "родной" .NET Framework 4.0, проверяя версию этой программной платформы, установленную в ОС на данный момент. Если версия .NET Framework ниже 4.0, то она обновляется до 4.0 и всё прекрасно компилируется. Если версия .NET Framework выше 4.0, то всё оставляется как есть: IDE успешно завершает установку, но при компиляции ВСЕХ приложений выскакивает данная ошибка. Более того, ошибка была замечена даже при наличии в системе .NET Framework версии 4.0, но отличающейся от "родной" припиской вроде "Beta" или "Release Candidate".

ВАРИАНТЫ РЕШЕНИЙ:
1. Отключить инкрементную линковку в опциях Проекта.
В главном меню MSVC++2010 выбираем: Проект->Свойства->Свойства конфигурации->Компоновщик(Linker)->Включить инкрементное построение (Incremental Linking). Данный пункт по умолчанию включен для всех новых проектов. Выставляем его в Нет (No). И жмём OK. Инкрементное построение заметно сокращает время компилирования больших проектов. Но в нашем случае его отсутствие некритично.
2. Удалить (переместить в другое место) утилиту cvtres.exe из каталога bin установленной MS Visual C++ 2010.
В нашем случае (Win7 x64) полный путь до данного файла такой: C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\cvtres.exe . Официальное название данного приложения - Microsoft® Resource File To COFF Object Conversion Utility (утилита конвертации файлов двоичных ресурсов в Component Object File Format). Опытным путём установлено, что при линковке IDE "спотыкается" именно об него.
3. Удалить из системы все версии .NET Framework (включая языковые пакеты и всякие профайлеры, если есть) и саму MS Visual C++ 2010.
Всё вышеперечисленное можно без труда найти в меню "Программы и компоненты" (MS Windows Vista/7/8). Затем заново установить MS Visual C++ 2010. При этом автоматом установится .NET Framework 4.0, идущий с ней в наборе.

Третий пункт - самый долгий. На деле почти всегда хватает выполнения первых двух. Данным вопросом озадачивались ребята здесь: https://www.cyberforum.ru/cpp-beginners/thread637174.html?ysclid=l3akpmanrq(external link). После этого линковка (=компоновка) проходит идеально. Подобные "костыли" в IDE от Майков - не редкость. Можно предположить, что команда с головой ударилась в тестирование .NET-возможностей MSVC++2010, совсем забыв о Win32-направлении (либо признав его бесперспективным).


Отключим инкрементную компоновку в свойствах открытого Проекта. Для MS Visual C++ 2010 порядок следующий:
  • Убедись, что MSVC++2010 запущена и в ней открыт Проект, с которым работаешь.
В Обозревателе решений видим: "Решение "..."", а строкой ниже жирным шрифтом название Проекта. Обычно с тем же названием (если не менял вручную).
  • В Обозревателе решений щёлкаем правой кнопкой мыши по названию Проекта.
  • Во всплывающем контекстном меню выбираем "Свойства".
  • В появившемся окне установки свойств Проекта жмём Свойства конфигурации -> Компоновщик -> Общие (Configuration Properties -> Linker -> General), в правой части в строке "Включить инкрементную компоновку" ставим значение Нет (/INCREMENTAL:NO).
  • Жмём ОК.

Удаляем файл cvtres.exe

В нашем случае (Win7 x64) полный путь до данного файла такой: C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\cvtres.exe
В Win32-кодинге он особо ни на что не влияет. Зато без него линковка идёт как по маслу.

Прописываем библиотеки d3dx8.lib, d3d8.lib и Winmm.lib в окне "Дополнительные зависимости" (Additional dependencies) компоновщика (Linker)

Данный этап проходил даже автор кода Jim Adams в 2002 году, прогая на MSVC++6.0. В начальных комментариях листинга WinMain.cpp он намекнул, что библиотеки D3D8.LIB и D3DX8.LIB необходимо явно указывать в списке дополнительных зависимостей линкера ( = компоновщика):
Фрагмент WinMain.cpp
...
Required libraries:
  WINMM.LIB, D3D8.LIB and D3DX8.LIB
...

Библиотеки D3D8.LIB и D3DX8.LIB расположены в папке с установленным DirectX SDK 8 (в нашем случае по пути C:\DXSDK8\lib\), пути к которой мы прописали выше.
Т.к. мы создаём исполняемое приложение (исполняемый .exe-файл), а не библиотеку, то после компиляции полученный объектный модуль сразу линкуется путём вызова компоновщика (=linker). Так вот, этот самый компоновщик по ранее прописанным каталогам данные библиотеки не ищет. Поэтому их необходимо указывать отдельно в окне настроек Проекта, в разделе "Компоновщик". ОК, начинаем.
  • Убедись, что MSVC++2010 запущена и в ней открыт наш текущий Проект.
  • В Главном меню MS Visual C++ 2010 выбираем Проект -> Свойства (Project -> Properties).
  • В появившемся окне установки свойств Проекта последовательно щёлкаем по раскрывающимся ветвям иерархического дерева: Свойства конфигурации -> Компоновщик -> Ввод (Configuration Properties -> Linker -> Input).
  • В правой части, напротив строки "Дополнительные зависимости" жмём кнопку с чёрным треугольником.
  • В всплывающем списке жмём "Изменить".
  • В появившемся окне "Дополнительные зависимости" в верхнем поле ввода прописываем в столбик (один под другим) имена файлов трёх библиотек:
d3dx8.lib
d3d8.lib
Winmm.lib
Image
Библиотека Winmm.lib отвечает за мультимедиа-возможности приложения и расположена в каталоге с установленным Windows SDK.
  • Жмём ОК, ОК.
  • Сохрани Решение (Файл->Сохранить все)

Отключаем использование компоновщиком библиотеки libci.dll

Да, даже на данном этапе компиляция Проекта выдаст ошибку. Библиотека libci.dll использовалась в VisualStudio когда-то очень давно, и в современных версях IDE её нет. Тем не менее компоновщик почти всегда вызывает её при компиляции, ругаясь на её отсутствие. Самый простой способ это исправить - запретить использовать libci.dll по умолчанию.
ОК, начнём.
  • Убедись, что MSVC++2010 запущена и в ней открыт Проект, с которым работаешь в данный момент.
  • В Главном меню MS Visual C++ 2010 выбираем Проект -> Свойства (Project -> Properties).
  • В появившемся окне установки свойств Проекта последовательно щёлкаем по раскрывающимся ветвям иерархического дерева: Свойства конфигурации -> Компоновщик -> Командная строка (Configuration Properties -> Linker -> Command Promt).
В правой части, внизу, видим поле ввода "Дополнительные параметры".
  • Пишем в него строку: /NODEFAULTLIB:libci
Image
  • Жмём ОК.
  • Сохрани Решение (Файл->Сохранить все).

Компилируем Проект Draw2D_01

Наконец, наш тестовый Проект готов к компиляции.
  • Жми кнопку с зелёным треугольником на панели инструментов главного окна MSVC++2010 или F5 на клавиатуре.
Image
Скомпилированное .exe-приложение в нашем случае (Win7 x64) расположено по пути C:\Users\User1\Documents\Visual Studio 2010\Projects\Draw2D_01\Debug .
После компиляции приложение Draw2D_01 автоматически запустится и покажет окно с белым квадратом на синем фоне. Согласно замыслу, в окне изображён квадрат, состоящий из 2-х треугольных полигонов с одной общей стороной. Объект (плоский 3D-квадрат) не имеет текстуры, т.к. её пока никто не готовил.

Готовим текстуру


Image
Рис.4 Текстурой может быть любой файл изображения, сконвертированный в формат .bmp или .jpg


Если ты внимательно изучил код WinMain.cpp данного примера, то наверняка обратил внимание на строку загрузки текстур в самом конце листинга:
Фргамент WinMain.cpp
...
  // Load the texture map
  D3DXCreateTextureFromFile(g_pD3DDevice, "Texture.bmp", &g_pTexture);

  return TRUE;
}
...

Здесь в качестве текстуры используется файл изображения с расширением .bmp . DirectX также поддерживает текстуры в формате .jpg .
По умолчанию, сразу после запуска, программа ищет 1 файл текстуры: Texture.bmp. Ищет в той же папке, что и исполняемый exe-файл. Организуем его.
  • Создай в Фотошопе или найди в Интернете любое изображение.
Например, изображение дерева (См. Рис.4). Желательно, чтобы изображение было небольшого размера и разрешения (например 256х256).
  • Конвертируй его в формат .bmp (например с помощью программ XNView или MS Paint) и переименуй в Texture.bmp.
  • Размести полученный bmp-файл в папке с полученным исполняемым .exe-файлом (по умолчанию C:\Users\<Имя пользователя>\Documents\Visual Studio 2010\Projects\Draw2D_01\Debug).
Именно здесь его будет по умолчанию искать наша программа.
  • Перезапусти приложение Draw2D_01.exe.
В этот раз наш полигональный квадрат покрыт текстурой.
Закрыть
noteОбрати внимание

Замечено, что в случае перекомпиляции и вызова приложения из MSVC++2010, текстуры часто не подгружаются. Поэтому просто открой Проводником Windows папку C:\Users\User1\Documents\Visual Studio 2010\Projects\Draw2D_01\Debug и запусти содержащейся в ней исполняемый файл Draw2D_01.exe.


Пример приложения, выводящее в окне 3D-объект с текстурой (Win32, DirectX8)


Создаём Проект приложения Draw3D_01

  • Создай пустой Проект с именем Draw3D_01.
Проект автоматически разместится внутри Решения с таким же именем. Весь процесс подробно расписан в статье MS Visual Cpp 2010 Express. Установка и указание путей к DirectX SDK.

Добавляем в Проект WinMain.cpp

Для чистоты эксперимента мы создали пустой Проект, т.е. без каких-либо файлов в нём. Создадим единственный файл с исходным кодом WinMain.cpp.
  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Файлы исходного кода" Проекта Draw3D_01.
  • Во всплывающем меню Добавить->Создать элемент...
Image
  • В появившемся окне выбери "Файл С++ (.cpp)" и в поле "Имя" введи WinMain.cpp. Жмём "Добавить".
Image
Добавленный файл сразу откроется в правой части MSVC++2010.
  • В только что созданном и открытом файле WinMain.cpp набираем следующий код:
WinMain.cpp
/**************************************************
WinMain.cpp
Chapter 6 3-D Drawing Demo (Draw3D_01)

Programming Role-Playing Games with DirectX
by Jim Adams (01 Jan 2002)

Required libraries:
  WINMM.LIB, D3D8.LIB and D3DX8.LIB
**************************************************/

// Include files
#include <windows.h>
#include <stdio.h>
#include "d3d8.h"
#include "d3dx8.h"

// Window handles, class and caption text
// Дескриптор окна, оконный класс и текст тулбара
HWND          g_hWnd;
HINSTANCE     g_hInst;
static char   g_szClass[]   = "Draw3DClass";
static char   g_szCaption[] = "Draw3D Demo by Jim Adams";

// The Direct3D and Device object
IDirect3D8       *g_pD3D       = NULL;
IDirect3DDevice8 *g_pD3DDevice = NULL;

// The 3-D vertex format and descriptor
// FVF-формат вершин и его дескриптор
typedef struct {
  FLOAT x, y, z;     // 3-D coordinates
  FLOAT u, v;        // Texture coordinates
} sVertex;
#define VERTEXFVF (D3DFVF_XYZ | D3DFVF_TEX1)

// Vertex buffer
// Вершинный буфер
IDirect3DVertexBuffer8 *g_pVB = NULL;

// Texture
IDirect3DTexture8 *g_pTexture = NULL;

// Function prototypes
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev,          \
                   LPSTR szCmdLine, int nCmdShow);
long FAR PASCAL WindowProc(HWND hWnd, UINT uMsg,              \
                           WPARAM wParam, LPARAM lParam);

BOOL DoInit();
BOOL DoShutdown();
BOOL DoFrame();
BOOL SetupMeshes();

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev,          \
                   LPSTR szCmdLine, int nCmdShow)
{
  WNDCLASSEX wcex;
  MSG        Msg;

  g_hInst = hInst;

  // Create the window class here and register it
  // Создаём оконный класс и регистрируем его
  wcex.cbSize        = sizeof(wcex);
  wcex.style         = CS_CLASSDC;
  wcex.lpfnWndProc   = WindowProc;
  wcex.cbClsExtra    = 0;
  wcex.cbWndExtra    = 0;
  wcex.hInstance     = hInst;
  wcex.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
  wcex.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wcex.hbrBackground = NULL;
  wcex.lpszMenuName  = NULL;
  wcex.lpszClassName = g_szClass;
  wcex.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);
  if(!RegisterClassEx(&wcex))
    return FALSE;

  // Create the Main Window
  // Создаём главное окно программы
  g_hWnd = CreateWindow(g_szClass, g_szCaption,
        WS_CAPTION | WS_SYSMENU,
        0, 0, 400, 400,
        NULL, NULL,
        hInst, NULL );
  if(!g_hWnd)
    return FALSE;
  ShowWindow(g_hWnd, SW_NORMAL);
  UpdateWindow(g_hWnd);

  // Run init function and return on error
  if(DoInit() == FALSE)
    return FALSE;

  // Start message pump, waiting for signal to quit
  // Стартуем процедуру выборки сообщений.
  // Выходим при получении сообщения WM_QUIT
  ZeroMemory(&Msg, sizeof(MSG));
  while(Msg.message != WM_QUIT) {
    if(PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE)) {
      TranslateMessage(&Msg);
      DispatchMessage(&Msg);
    }
    if(DoFrame() == FALSE)
      break;
  }

  // Run shutdown function
  DoShutdown();
  
  UnregisterClass(g_szClass, hInst);

  return Msg.wParam;
}

long FAR PASCAL WindowProc(HWND hWnd, UINT uMsg,              \
                           WPARAM wParam, LPARAM lParam)
{
  switch(uMsg) {
    case WM_DESTROY:
      PostQuitMessage(0);
      return 0;

  }

  return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

BOOL DoInit()
{
  D3DPRESENT_PARAMETERS d3dpp;
  D3DDISPLAYMODE        d3ddm;
  D3DXMATRIX matProj, matView;
  BYTE *Ptr;
  sVertex Verts[4] = {
      { -100.0f,  100.0f, 0.0f, 0.0f, 0.0f },
      {  100.0f,  100.0f, 0.0f, 1.0f, 0.0f },
      { -100.0f, -100.0f, 0.0f, 0.0f, 1.0f },
      {  100.0f, -100.0f, 0.0f, 1.0f, 1.0f }
    };

  // Do a windowed mode initialization of Direct3D
  // Инициализируем объект устройства Direct3D
  // для работы в оконном режиме
  if((g_pD3D = Direct3DCreate8(D3D_SDK_VERSION)) == NULL)
    return FALSE;
  if(FAILED(g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm)))
    return FALSE;
  ZeroMemory(&d3dpp, sizeof(d3dpp));
  d3dpp.Windowed = TRUE;
  d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
  d3dpp.BackBufferFormat = d3ddm.Format;
  d3dpp.EnableAutoDepthStencil = TRUE;
  d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
  if(FAILED(g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hWnd,
                                  D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                  &d3dpp, &g_pD3DDevice)))
    return FALSE;

  // Set the rendering states
  g_pD3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
  g_pD3DDevice->SetRenderState(D3DRS_ZENABLE, TRUE);

  // Create and set the projection matrix
  // Создаём матрицу проекции
  D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI/4.0f, 1.33333f, 1.0f, 1000.0f);
  g_pD3DDevice->SetTransform(D3DTS_PROJECTION, &matProj);

  // Create and set the view matrix
  // Создаём матрицу вида
  D3DXMatrixLookAtLH(&matView,                                \
                     &D3DXVECTOR3(0.0f, 0.0f, -500.0f),       \
                     &D3DXVECTOR3(0.0f, 0.0f, 0.0f),          \
                     &D3DXVECTOR3(0.0f, 1.0f, 0.0f));
  g_pD3DDevice->SetTransform(D3DTS_VIEW, &matView);

  // Create the vertex buffer and set data
  // Создаём вершинный буфер
  g_pD3DDevice->CreateVertexBuffer(sizeof(sVertex)*4, 0,      \
                          VERTEXFVF, D3DPOOL_DEFAULT, &g_pVB);
  g_pVB->Lock(0,0, (BYTE**)&Ptr, 0);
  memcpy(Ptr, Verts, sizeof(Verts));
  g_pVB->Unlock();

  // Load the texture map
  // Загружаем текстурную карту
  D3DXCreateTextureFromFile(g_pD3DDevice, "Texture.bmp", &g_pTexture);

  return TRUE;
}

BOOL DoShutdown()
{
  // Release vertex buffer
  // Очищаем вершинный буфер
  if(g_pVB != NULL)
    g_pVB->Release();

  // Release texture
  if(g_pTexture != NULL)
    g_pTexture->Release();

  // Release device and 3D objects
  if(g_pD3DDevice != NULL)
    g_pD3DDevice->Release();

  if(g_pD3D != NULL)
    g_pD3D->Release();

  return TRUE;
}

BOOL DoFrame()
{
  D3DXMATRIX matWorld;

  // Clear device backbuffer
  // Очищаем бэкбуфер устройства
  g_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 
                      D3DCOLOR_RGBA(0,64,128,255), 1.0f, 0);

  // Begin scene
  if(SUCCEEDED(g_pD3DDevice->BeginScene())) {

    // Create and set the world transformation matrix
	// Создаём мировую матрицу трансформации
    // Rotate object along Z-axis
	// Вращаем объект по оси Z
    D3DXMatrixRotationZ(&matWorld, (float)timeGetTime() / 1000.0f);
    g_pD3DDevice->SetTransform(D3DTS_WORLD, &matWorld);

    // Set the vertex stream, shader, and texture
	// Назначаем вершинный поток, шейдер и текстуру
    g_pD3DDevice->SetStreamSource(0, g_pVB, sizeof(sVertex));
    g_pD3DDevice->SetVertexShader(VERTEXFVF);
    g_pD3DDevice->SetTexture(0, g_pTexture);

    // Draw the vertex buffer
	// Отрисовываем содержимое вершинного буфера
    g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

    // Release texture
    g_pD3DDevice->SetTexture(0, NULL);

    // End the scene
    g_pD3DDevice->EndScene();
  }

  // Display the scene
  // Выводим сценц на экран
  g_pD3DDevice->Present(NULL, NULL, NULL, NULL);

  return TRUE;
}

  • Сохрани Решение (Файл -> Сохранить все).
Данный исходный код целиком взят из примера к книге Programming Role-Playing Games with DirectX by Jim Adams (01 Jan 2002). Автор прогал на MS Visual C++ 6.0 (вышла в свет в 1998 г.) c установленным DirecX SDK 8.0. И если в нашем случае в качестве Graphic API мы юзаем практически идентичный DirectX SDK 8.1, то IDE у нас совсем другая (MS Visual C++ 2010 Express Edition). Нет, не с целью усложнения задачи. Просто MS Visual C++ 6.0 в своё время стоила 500 USD. Сейчас её, в принципе, можно поискать в торрентах. (За всё, что ты там скачаешь, администрация Igrocoder.ru ответственности не несёт!) Но использование платного/пиратского софта не соответствует концепции сайта Igrocoder.ru .
Поэтому наш выбор:
  • Бесплатная IDE MS Visual C++ 2010 Express Edition;
  • Бесплатный DirectX SDK 8.1 .
С момента выхода MS Visual C++ 6.0 прошло немало времени. С MSVC++2010 Express их разделяют аж 12 лет. Из-за этого вышеприведённый код (написанный в начале 2002 г.) на данном этапе в MSVC++2010 Express компилироваться не будет, выдавая многочисленные ошибки. Даже с настроенными путями к DirectX SDK.
Сейчас мы это исправим.

Готовим Проект Draw3D_01 к компиляции

Для успешной компиляции изменим настройки (=свойства) текущего Проекта Draw3D_01, созданного в MSVC++2010. При этом сам код из книги 2002 года останется нетронутым.
  • Укажи пути к DirectX SDK 8.1
  • Укажи пути к Windows SDK
  • Выбери в свойствах Проекта многобайтовую кодировку
  • Отключи инкрементную компоновку (incremental linking)
  • Пропиши библиотеки d3dx8.lib, d3d8.lib и WINMM.LIB в окне "Дополнительные зависимости" (Additional dependencies) компоновщика (Linker)
  • Отключи использование компоновщиком библиотеки libci.dll
Все эти действия подробно рассмотрены выше, при подготовке к компиляции Проекта Draw2D_01. Здесь всё выполняется аналогичным образом.
  • Сохрани Решение (Файл->Сохранить все).

Компилируем Проект Draw3D_01

Наконец, наш тестовый Проект готов к компиляции.
  • Жми кнопку с зелёным треугольником на панели инструментов главного окна MSVC++2010 или F5 нак лавиатуре.
После компиляции приложение Draw3D_01 автоматически запустится и покажет вращаюшийся белый квадрат. Программа ищет в каталоге с .exe-файлом файл текстуры Texture.bmp .
  • Подготовь файл текстуры Texture.bmp, следуя инструкциям в предыдущем примере.
  • Перезапусти приложение Draw3D_01.exe .
На экране покажется вращающийся квадрат с текстурой.

Задание на дом


В папке с примерами (samples) DirectX SDK 8 есть пример "Tut05_Textures", также демонстрирующий загрузку текстуры и нанесение нё на 3D-объект. В нашем случае путь до него такой: c:\DXSDK8\samples\Multimedia\Direct3D\Tutorials\Tut05_Textures\.
  • Найди в папке с данным примером файл исходного кода Textures.cpp.
  • Создай новый Проект Textures01 в MSVC++2010 и скомпилируй исходный код, содержащийся в Textures.cpp, следуя инструкциям из примера Draw2D_01.
Обрати внимание, что, не найдя текстуры, DirectX-пример даже не покажет окна программы. Так что перед запуском размести в одной папке скомпилированный .exe-файл и файл текстуры banana.bmp (найдёшь в папке с этим DirectX-примером).
  • Зрительно сравни исходные коды примеров техники билбординга Джима Адамса и MS DirectX SDK 8, выявив сходства и различия.

Альфа-смешивание (Alpha Blending)

Представь, что ты находишься внутри одного из самых высоких зданий в мире. Подойдя к окну и посмотрев вниз, ты увидишь весь город внизу. Нежно голубой цветовой тон (hue) оконного стекла придаёт всему увиденному умиротворяющий затенённый вид, схожий с тем, что можно наблюдать на утреннем небе в ясную погоду.
А теперь представь ту же самую сцену, переложенную на язык 3D-графики. Весь 3D-мир состоит из полигонов, которые (чаще всего) представляют собой "твёрдые" непрозрачные объекты. Ты не можешь посмотреть сквозь них и увидеть, что спрятано за ними. Но что, если в игре понадобится посмотреть в (закрытое) окно? Как насчёт голубого оттенка, который придаёт виду за окном виртуальное "стекло"? Та же история и с т.н. прозрачными щелями (transparent blits), т.е. с полигонами, у которых есть прозрачные участки. Как же вывести частично прозрачный объект, например стену с отверстием в ней? Отверстие абсолютно прозрачно. Даже с учётом того, что стена твёрдая, ты можешь видеть через отверстие другие объекты, которые эта стена скрывает.
Такие эффекты возможны благодаря применению техники т.н. альфа-блендинга g (alpha-blending). Она позволяет изменять степень прозрачности (transparency) полигона таким образом, что сквозь него можно видеть другие объекты сцены. В случае окрашенного в цвет полигона, его цвет будет смешиваться с цветом других объектов, которые тот перекрывает. Альфа-блендинг также поддерживает текстуры прозрачности, что позволяет создавать некоторые оч. зрелищные эффекты. Степень прозрачности объекта задаёт специальная величина - альфа-значение (alpha value).
Закрыть
noteОбрати внимание

Альфа-канал (alpha channel) представляет собой значение (value). Точно также как значения цветовых компонентов rgb (red, green, blue). Оно указывает степень прозрачности, применимое к каждому пикселю поверхности, обладающей альфа-каналом. В режимах цветности (color modes) текстур альфа-канал может быть значением величиной от 1 до 8 бит. Если у тебя 8-битный альфа-канал, то возможные значения прозрачности могут изменяться от 0 до 255. В случае 4-битного альфа-канала - от 0 до 15.
Dierct3D использует альфа-значение несколькими способами. Например, при использовании текстур, можно указать формат пикселей (pixel format), использующий альфа-канал.


Включаем альфа-смешивание (Enabling Alpha Blending)

Включение альфа-смешивание выполняется с помощью уже знакомой функции IDirect3DDevice8::SetRenderState, включающей и отключающей всевозможные стейты рендеринга (render states). Первым параметром здесь указывается D3DRS ALPHABLENDENABLE:
// g_pD3DDevice - предварительно проинициализированный объект устройства Direct3D.
// Включаем альфа-смешивание.
g_pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); // Назначаем тип альфа-смешивания.

g_pD3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
g_pD3DDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

Для отключения альфа-смешивания пишем так:
g_pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);

В приведённом выше коде видны назначения двух дополнительных рендер-стейтов (D3DRS_SRCBLEND и D3DRS_DESTBLEND). Они указывают Direct3D использовать при рендеринге определённые альфа-значения (D3DBLEND_SRCALPHA и D3DBLEND_INVSRCALPHA). Более подробно их рассмотрим позднее.
Закрыть
noteПримечание

Подробнее о рендер-стейтах и огромной функции SetRenderState, которая их устанавливает, читай в отдельной статье Функция SetRenderState и её параметры. Там, правда, всё по DirectX 9. Но большая часть рендер-стейтов оттуда также работает в DirectX 8.


Рисуем с альфа-смешиванием (Drawing with Alpha Blending)

Единственная инфа, которая нужна для начала рисования с альфа-смешиванием, это способ добавления альфа-значений в кастомную FVF-структуру. Это делается путём добавления в FVF-структуру цветового компонента рассеянного (diffuse) света. При определении рассеянного компонента тут же указываем альфа-значение.
Следующий пример устанавливает кастомную FVF-структуру, хранящую 3D-координаты + цветовой компонент рассеянного света (diffuse color component), который теперь включает в себя альфа-значение:
// Кастомная (пользовательская) FVF-структура и её дескриптор.

typedef struct {
	FLOAT x, y, z;
	D3DCOLOR diffuse;
} sVertex;

#define VertexFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE)

// Определяем 3 вершины в локальном массиве.
sVertex Verts[3] =
{
	(0.0f, 100.0f, 0.0f, D3DCOLOR_RGBA(255, 0, 0, 64)),
	(100.0f, -100.0f, 0.0f, D3DCOLOR_RGBA(0,255,0,128)),
	(-100.0f, -100.0f, 0.0f, D3DCOLOR_RGBA(0,0,255,255))
};

Первой вершине назначается красный цвет и 1/4 прозрачности (1/4 цвета будет прозрачной) Вторая вершина зелёная, с применением 1/2 просвечиваемости (translucency). Третья вершина синяя и полностью непрозрачна (opaque). Если сюда добавить текстурные координаты и назначить валидную текстуру, то увидим интересный эффект, окрашивающий края текстуры в разные цвета.

Блиттинг прозрачности с альфа-тестированием (Transparent Blitting with Alpha Testing)


Image
Рис.5 Прозрачность с ключевым цветом. Источник: https://www.yaldex.com/games-programming/0672323699_ch07lev1sec8.html


Альфа-тестирование - это специальная техника тестирования альфа-значений пикселей перед их выводом на экран. Пиксели с альфа-значениями, не попадающими в выбранный диапазон, просто отбрасываются и, таким образом, не достигают стадии рендеринга. Подобно тому, как мы выше достигли эффектов полупрозрачности, альфа тестирование применяется для проверки полигонов, содержащих полностью прозрачные области.
Для примера возьмём всё ту же дыру в стене. Представь, что стена - это полигон и тебе надо проделать круглое отверстие в её центре. При этом важно, чтобы стена была полностью непрозрачной ("твёрдой"). Дыра будет полностью прозрачной. Для достижения данного эффекта применяют специальную технику под названием прозрачный блит (transparent blit, быстрое копирование прозрачности), которая позволяет исключать определённые участки текстуры во время рендеринга. Т.е. можно создавать "твёрдые" поверхности со "сквозными" отверстиями в них.
Секрет блитирования прозрачности кроется в установке текстуры и назначения ей одного из цветов в качестве "ключа" прозрачности (ключевой цвет, color key). Ключевой цвет - это цвет, который НЕ будет прорисован при рендеринге полигона. К примеру, у нас есть текстура, с изображением персонажа на чёрном фоне (См. Рис.5). При назначении чёрного цвета в качестве ключевого, Direct3D не станет отрисовывать чёрные пиксели, таким образом выведя на экран только фигурку персонажа.
На деле прозрачность определяет не отдельный цвет пикселей, а его значение, обозначенное в качестве альфа-значения. Чтобы пиксель был полностью прозрачным, его альфа-значение должно быть установлено в 0. Чтобы пиксель был отрисован, его альфа-значение должно быть максимальным (обычно 255). Пиксели, которые совпадают с ключевым цветом, имеют альфа-значение 0, а во всех остальных выставлена максимальная величина альфа-значения.

Загрузка текстур с ключевым цветом

При использовании альфа-тестирования в данном примере нам нет необходимости указывать рассеянный (diffuse) компонент цвета света в FVF-структуре или её дескрипторе. Альфа-значения хранятся прямо в структуре данных пикселей текстуры (texture pixel data structure) LPDIRECT3DTEXTURE8, создаваемой при загрузке текстуры из файла. Для назначания альфа-значений в текстуре данных пикселей структуры применяется расширенный аналог функции D3DCreateTextureFromFile - D3DCreateTextureFromFileEx. Вот её прототип:
Прототип функции D3DXCreateTextureFromFileEx
HRESULT D3DXCreateTextureFromFileEx(
	LPDIRECT3DDEVICE8 pDevice, // Объект устройства, на котором всё создаётся.
	LPCSTR pSrcFile, // Имя файла загружаемого файла текстуры.
	UINT Width, // Обычно тут стоит D3DX_DEFAULT.
	UINT Height, // Обычно тут стоит D3DX_DEFAULT.
	UINT MipLevels, // Обычно тут стоит D3DX_DEFAULT.
	DWORD Usage, // Обычно тут стоит 0.
	D3DFORMAT Format, // Формат цветности пикселей (Color format) текстуры.
	D3DPOOL Pool, // Обычно тут стоит D3DPOOL_MANAGED.
	DWORD Filter, // Обычно тут стоит D3DX_FILTER_TRIANGLE
	DWORD MipFilter // Обычно тут стоит D3DX_FILTER_TRIANGLE.
	D3DCOLOR ColorKey, // А вот и он, ключевой цвет (альфа-значение)!
	D3DXIMAGE_INFO* pSrcInfo, // Обычно тут стоит NULL.
	PALETTEENTRY* pPalette, // Используемая палитра. Обычно тут стоит NULL.
	LPDIRECT3DTEXTURE8* ppTexture // Указатель на создаваемый объект текстуры.
	);

В большинстве параметров здесь обычно выставляются значения по умолчанию. Единственное, над чем надо подумать, это:
  • имя файла текстуры (обычно это файл растрового изображения),
  • объект устройства,
  • формат цветности (color format; значение типа D3DFMT_*, обязательно с примением альфа-значения, вроде D3DFMT_A8R8G8B8),
  • ключевой цвет (цвет шаблонной "выключки"; также в формате вида D3DFMT_*).
При указании ключевого цвета используй макросы D3DCOLOR_RGBA или D3DCOLOR_COLORVALUE. Например, нам надо исключить из отрисовки чёрный цвет. В этом случае альфа значение будет выглядеть так:
D3DCOLOR_RGBA(0,0,0,255);

Здесь значение 255 является к альфа-значением. Далее при загрузке файла текстуры в параметре ColorKey также необходимо указать 255 в качестве альфа-значения.
При работе с нерастровыми файлами изображений (например текстуры формата TGA), в которые уже включены альфа-значения, альфа-значения в фалах текстур и те, что указаны при вызове функции D3DXCreateTextureFromFileEx для их загрузки, должны совпадать. В данной статье мы используем только файлы растровых изображений (BMP). После установки альфа-значений для каждого пикселя текстуры, осталось выполнить альфа-тестирование для "отсечения" пикселей, чей цвет совпадает с альфа-значением.

Включаем альфа-тестирование (Enabling Alpha Testing; DirectX 8)

Итак, текстура загружена и значения ключевого цвета и альфа-значение установлены. Теперь можно подключить альфа-тестирование путём добавления в цикл инициализации или рендеринга следующего кода:
// g_pD3DDevice - предварительно проинциализированный объект устрйства Direct3D.
g_pD3DDevice->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE);
g_pD3DDevice->SetRenderState(D3DRS_ALPHAREF, 0x08);
g_pD3DDevice->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATERQUAL);

Стейт D3DRS_ALPHAREF вообще волшебный, т.к. указывает Direct3D, какие альфа-значения допускать к рендерингу (принимает значения от 0 до 255). В данном примере как только все 3 вышеприведённые функции будут вызваны, все пиксели с альфа-значением ниже 8 просто отсеются! В случае корректного указания ключевого цвета (color key) данные 3 функции исключат все пиксели с околонулевым альфа-значением, делая данные области текстуры прозрачными.

Пример прозрачного блиттинга (A Transparent Blitting Example; DirectX 8)

Вот небольшой пример, который загружает картинку с кнопкой и показывает её на экране. Чёрные пиксели отсекаются (excluded), делая края кнопки прозрачными.
// g_pD3DDevice - предварительно проинциализированный объект устрйства Direct3D.

// Кастомная FVF-структура и её дескриптор.
typedef struct
{
	FLOAT x, y, z, rhw; // Экранные координаты.
	FLOAT u, v; // Текстурные координаты.
} sVertex;

#define VertexFVF(D3DFVF_XYZRHW|D3DFVF_TEX1)

// Вершинный буфер и текстура.
IDirect3DVertexBuffer8 *g_pVB = NULL;
IDirect3DTexture8 *g_pTexture = NULL;

// Настраиваем вершинный буфер и текстуру.

// Допустим, у нас рендеринг в окне размером 400х400 пикселей.
BYTE *Ptr;
sVertex Verts[4] =
{
	{0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f},
	{399.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f},
	{0.0f, 399.0f, 0.0f, 1.0f, 0.0f, 1.0f},
	{ 3 99.0f, 3 99.0f, 0.0f, 1.0f, 1.0f, 1.0f}
}

// Создаём вершинный буфер и прочее.
g_pD3DDevice->CreateVertexBuffer(sizeof(sVertex)*4, 0, VertexFVF, D3DPOOL_DEFAULT, &g_pVB);
g_pVB->Lock(0, 0, (BYTE**)&Ptr, 0);
memcpy(Ptr, Verts, sizeof(Verts));
g_pVB->Unlock() ;

// Загружаем текстуру.
D3DXCreateTextureFromFileEx(g_pD3DDevice, "button.bmp", D3DX_DEFAULT, D3DX_DEFAULT,
	D3DX_DE FAULT, 0, D3DFTM_A8R8G8B8, D3DPOOL_MANAGED, D3DX_FILTER_TRIANGLE,
	D3DX_FILTER_TRIANGLE, D3DCOLOR_RGBA(0, 0, 0, 255), NULL, NULL, &g_pTexture);

// Настраиваем альфа-тестирование.
g_pD3DDevice->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE);
g_pD3DDevice->SetRenderState(D3DRS_ALPHAREF, 0x01);
g_pD3DDevice->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL);

// Очищаем бэкбуфер устройства Direct3D.
g_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_RGBA(0, 128, 128, 255), 1.0f, 0);

if(SUCCEEDED(g_pD3DDevice->BeginScene ()))
{
	// Назначаем поток-источник (stream source) буферу частиц.
	g_pD3DDevice->SetStreamSource(0, g_pVB, sizeof(sVertex));
	
	// Назначаем вершинный шейдер кастомному FVF-формату частиц.
	g_pD3DDevice->SetVertexShader(VertexFVF);
	
	// Назначаем текстуру.
	g_pD3DDevice->SetTexture(0, g_pTexture); // Рисуем соедржимое вершинного буфера.
	g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
	g_pD3DDevice->EndScene();
	
	// Убираем текстуру из памяти (прибираемся).
	g_pD3DDevice->SetTexture(0, NULL);
	
	// Выключаем альфа-тестирование
	g_pD3DDevice->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE)

}

// Переключаем (flip) экранные буферы, отображая нарисованное.
g_pD3DDevice->Present(NULL, NULL, NULL, NULL);


Пример приложения, выводящего два пересекающихся прямоугольника (Win32, DirectX8)

Один из прямоугольников частично прозрачный. Демонстрируем альфа-смешивание.

Создаём Проект приложения Alpha01

  • Создай пустой Проект с именем Alpha01.
Проект автоматически разместится внутри Решения с таким же именем. Весь процесс подробно расписан в статье MS Visual Cpp 2010 Express. Установка и указание путей к DirectX SDK.

Добавляем в Проект WinMain.cpp

Для чистоты эксперимента мы создали пустой Проект, т.е. без каких-либо файлов в нём. Создадим единственный файл с исходным кодом WinMain.cpp.
  • В "Обозревателе решений" главного окна MSVC++2010 щёлкни правой кнопкой мыши по папке (в терминологии Майкрософт это не папки, а фильтры!) "Файлы исходного кода" Проекта Alpha01.
  • Во всплывающем меню Добавить->Создать элемент...
Image
  • В появившемся окне выбери "Файл С++ (.cpp)" и в поле "Имя" введи WinMain.cpp. Жмём "Добавить".
Image
Добавленный файл сразу откроется в правой части MSVC++2010.
  • В только что созданном и открытом файле WinMain.cpp набираем следующий код:
WinMain.cpp
/**************************************************
WinMain.cpp
Chapter 6 Alpha Blending Demo

Programming Role-Playing Games with DirectX
by Jim Adams (01 Jan 2002)

Required libraries:
  D3D8.LIB
**************************************************/

// Include files
#include <windows.h>
#include <stdio.h>
#include "d3d8.h"

// Window handles, class and caption text
// Дескриптор окна, класс и текст тулбара
HWND          g_hWnd;
HINSTANCE     g_hInst;
static char   g_szClass[]   = "AlphaClass";
static char   g_szCaption[] = "Alpha Blending Demo by Jim Adams";

// The Direct3D and Device object
IDirect3D8       *g_pD3D       = NULL;
IDirect3DDevice8 *g_pD3DDevice = NULL;

// The 2-D vertex format and descriptor
// Кастомный FVF-формат и его дескриптор
typedef struct {
  FLOAT x, y, z;     // 2-D coordinates
  FLOAT rhw;         // rhw
  D3DCOLOR Diffuse;  // Diffuse color component
} sVertex;
#define VERTEXFVF (D3DFVF_XYZRHW | D3DFVF_DIFFUSE)

// Vertex buffer
// Вершинный буфер
IDirect3DVertexBuffer8 *g_pVB      = NULL;

// Function prototypes
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev,          \
                   LPSTR szCmdLine, int nCmdShow);
long FAR PASCAL WindowProc(HWND hWnd, UINT uMsg,              \
                           WPARAM wParam, LPARAM lParam);

BOOL DoInit();
BOOL DoShutdown();
BOOL DoFrame();
BOOL SetupMeshes();

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev,          \
                   LPSTR szCmdLine, int nCmdShow)
{
  WNDCLASSEX wcex;
  MSG        Msg;

  g_hInst = hInst;

  // Create the window class here and register it
  wcex.cbSize        = sizeof(wcex);
  wcex.style         = CS_CLASSDC;
  wcex.lpfnWndProc   = WindowProc;
  wcex.cbClsExtra    = 0;
  wcex.cbWndExtra    = 0;
  wcex.hInstance     = hInst;
  wcex.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
  wcex.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wcex.hbrBackground = NULL;
  wcex.lpszMenuName  = NULL;
  wcex.lpszClassName = g_szClass;
  wcex.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);
  if(!RegisterClassEx(&wcex))
    return FALSE;

  // Create the Main Window
  g_hWnd = CreateWindow(g_szClass, g_szCaption,
        WS_CAPTION | WS_SYSMENU,
        0, 0, 400, 400,
        NULL, NULL,
        hInst, NULL );
  if(!g_hWnd)
    return FALSE;
  ShowWindow(g_hWnd, SW_NORMAL);
  UpdateWindow(g_hWnd);

  // Run init function and return on error
  if(DoInit() == FALSE)
    return FALSE;

  // Start message pump, waiting for signal to quit
  // Стартуем процедуру выборки сообщений.
  // При поступлении сообщения WM_QUIT - закрываем окно.
  ZeroMemory(&Msg, sizeof(MSG));
  while(Msg.message != WM_QUIT) {
    if(PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE)) {
      TranslateMessage(&Msg);
      DispatchMessage(&Msg);
    }
    if(DoFrame() == FALSE)
      break;
  }

  // Run shutdown function
  DoShutdown();
  
  UnregisterClass(g_szClass, hInst);

  return Msg.wParam;
}

long FAR PASCAL WindowProc(HWND hWnd, UINT uMsg,              \
                           WPARAM wParam, LPARAM lParam)
{
  switch(uMsg) {
    case WM_DESTROY:
      PostQuitMessage(0);
      return 0;

  }

  return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

BOOL DoInit()
{
  D3DPRESENT_PARAMETERS d3dpp;
  D3DDISPLAYMODE        d3ddm;
  BYTE *Ptr;
  sVertex Verts[8] = {
      { 100.0f, 100.0f, 1.0f, 1.0f, D3DCOLOR_RGBA(0,64,128,255) },
      { 300.0f, 100.0f, 1.0f, 1.0f, D3DCOLOR_RGBA(0,64,128,255) },
      { 100.0f, 300.0f, 1.0f, 1.0f, D3DCOLOR_RGBA(0,64,128,255) },
      { 300.0f, 300.0f, 1.0f, 1.0f, D3DCOLOR_RGBA(0,64,128,255) },
      {  50.0f, 150.0f, 1.0f, 1.0f, D3DCOLOR_RGBA(128,0,0,128)  },
      { 350.0f, 150.0f, 1.0f, 1.0f, D3DCOLOR_RGBA(128,0,0,128)  },
      {  50.0f, 350.0f, 1.0f, 1.0f, D3DCOLOR_RGBA(128,0,0,128)  },
      { 350.0f, 350.0f, 1.0f, 1.0f, D3DCOLOR_RGBA(128,0,0,128)  }
    };

  // Do a windowed mode initialization of Direct3D
  // Выполняем инициализацию объекта Direct3D для оконного режима
  if((g_pD3D = Direct3DCreate8(D3D_SDK_VERSION)) == NULL)
    return FALSE;
  if(FAILED(g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm)))
    return FALSE;
  ZeroMemory(&d3dpp, sizeof(d3dpp));
  d3dpp.Windowed = TRUE;
  d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
  d3dpp.BackBufferFormat = d3ddm.Format;
  d3dpp.EnableAutoDepthStencil = FALSE;
  if(FAILED(g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hWnd,
                                  D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                  &d3dpp, &g_pD3DDevice)))
    return FALSE;

  // Create the vertex buffer and set data
  // Создаём вершинный буфер и размещаем в нём данные.
  g_pD3DDevice->CreateVertexBuffer(sizeof(sVertex)*8, 0,      \
                          VERTEXFVF, D3DPOOL_DEFAULT, &g_pVB);
  g_pVB->Lock(0,0, (BYTE**)&Ptr, 0);
  memcpy(Ptr, Verts, sizeof(Verts));
  g_pVB->Unlock();

  return TRUE;
}

BOOL DoShutdown()
{
  // Release vertex buffer
  if(g_pVB != NULL)
    g_pVB->Release();

  // Release device and 3D objects
  if(g_pD3DDevice != NULL)
    g_pD3DDevice->Release();

  if(g_pD3D != NULL)
    g_pD3D->Release();

  return TRUE;
}

BOOL DoFrame()
{
  // Clear device backbuffer
  // Очищаем бэкбуфер
  g_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET,               \
                           D3DCOLOR_RGBA(0,0,0,255), 1.0f, 0);

  // Begin scene
  if(SUCCEEDED(g_pD3DDevice->BeginScene())) {

    // Set the vertex stream and shader
	// Назначаем вершинный поток и шейдер
    g_pD3DDevice->SetStreamSource(0, g_pVB, sizeof(sVertex));
    g_pD3DDevice->SetVertexShader(VERTEXFVF);

    // Disable alpha blending and draw 2 polygons
	// Отключаем альфа-смешивание и рисуем 2 полигона
    g_pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
    g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

    // Enable alpha blending and draw 2 polygons
	// Включаем альфа-смешивание и рисуем 2 полигона
    g_pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
    g_pD3DDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
    g_pD3DDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
    g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 2);

    // End the scene
    g_pD3DDevice->EndScene();
  }

  // Display the scene
  // Выводим сцену на экран
  g_pD3DDevice->Present(NULL, NULL, NULL, NULL);

  return TRUE;
}

  • Сохрани Решение (Файл -> Сохранить все).

Готовим Проект Alpha01 к компиляции

Для успешной компиляции изменим настройки (=свойства) текущего Проекта Alpha01, созданного в MSVC++2010. При этом сам код из книги 2002 года останется нетронутым.
  • Укажи пути к DirectX SDK 8.1
  • Укажи пути к Windows SDK
  • Выбери в свойствах Проекта многобайтовую кодировку
  • Отключи инкрементную компоновку (incremental linking)
  • Пропиши библиотеку d3d8.lib в окне "Дополнительные зависимости" (Additional dependencies) компоновщика (Linker)
  • Отключи использование компоновщиком библиотеки libci.dll
Все эти действия подробно рассмотрены выше, при подготовке к компиляции Проекта Draw2D_01. Здесь всё выполняется аналогичным образом.
  • Сохрани Решение (Файл->Сохранить все).

Компилируем Проект Alpha01

Наконец, наш тестовый Проект готов к компиляции.
  • Жми кнопку с зелёным треугольником на панели инструментов главного окна MSVC++2010 или F5 на клавиатуре.
После компиляции приложение Alpha01 автоматически запустится и покажет два пересекающихся прямоугольника, один из которых частично не прозрачен.

Источники


1. Adams J. Programming Role Playing Games with DirectX 8.0. - Premier Press, 2002


Последние изменения страницы Пятница 27 / Май, 2022 12:01:37 MSK

Последние комментарии wiki

No records to display

Search Wiki Page

Точное совпадение

Категории

|--> C#
|--> C++